<?php

if (!function_exists('pdo_fn_str_create_string')) {

    /* 作者:江湖一键客 QQ:82530662
     +--------------------------------------------------------------------------------+
     | PHP PDO SDK 命名规范与函数返回错误码规范                                       |
     +--------------------------------------------------------------------------------+
     | Copyright (c) 2017-2017                                                        |
     +--------------------------------------------------------------------------------+
     | 0、类型前缀，无论函数或变量都需要一个类型前缀，类型前缀有                      |
     | $type=[array,int,float,str,date,void] 即混合类型、整数、浮点数、字符串、       |
     | 日期、空                                                                       |
     |                                                                                |
     | 1、任何函数名称的命名方式的正则表达式是pdo_fn_$type_[a-z]+(_[a-z0-9]+){0,}     |
     |                                                                                |
     | 2、任何常量的命名方式的正则表达式是WX_toUpperCase($type_)[A-Z]+(_[A-Z0-9]+){0,}|
     |                                                                                |
     | 3、任何局部变量的命名方式的正则表达式是[a-z]+([A-Z0-9]|_[a-z0-9]+){0,}         |
     |                                                                                |
     | 4、任何全局变量的命名方式的正则表达式是g_var_$type_[a-z]+(_[a-z0-9]+){0,}      |
     |                                                                                |
     | 5、任何回调函数的命名方式的正则表达式是cb_fn_$type_[a-z]+(_[a-z0-9]+){0,}      |
     |                                                                                |
     | 6、所有返回错误码对象都是这种形式:{"errcode":40013,"errmsg":"invalid appid"}   |
     +--------------------------------------------------------------------------------+
    */
    /** 把一个匿名函数的注释转换为字符串。
     * @param function $closure 匿名函数
     * @param array $replace 替换占位字符的内容，占位字符的格式正则表现为{[0-9]+}，比如{0}、{1} 其中数值对应$replace的索引
     * @return string 返回匿名函数内的注释内容
     * @throws ReflectionException
     */
    function pdo_fn_str_create_string($closure, $replace = NULL)
    {
        $func = new ReflectionFunction($closure);
        $start = $func->getStartLine() - 1;
        $end = $func->getEndLine() - 1;
        $filename = $func->getFileName();
        $code = implode("", array_slice(file($filename), $start, $end - $start + 1));
        $reg = "/\\/\\*([^\\0]+)\\*\\//";
        $match = array();
        preg_match($reg, $code, $match);
        if (count($match) > 0) {
            if (!$replace) {
                return preg_replace("/\\\\\\\/", "\\", preg_replace("/\*(\\\\\/)/", "*/", trim($match[1])));
            }
            if (count($replace) <= 0) {
                return preg_replace("/\\\\\\\/", "\\", preg_replace("/\*(\\\\\/)/", "*/", trim($match[1])));
            }
            $tmp1 = $match[1];
            for ($i = 0; $i < count($replace); $i++) {
                $r = $replace[$i];
                $tmp1 = str_replace("{" . $i . "}", $r, $tmp1);
            }
            return preg_replace('/\\\\\\\/', '\\', preg_replace("/\*(\\\\\/)/", "*/", $tmp1));
        }
        return "";
    }
}

if (!function_exists('pdo_fn_object_get_db_connect')) {

    /**创建一个数据库连接
     * @param string $host 数据库链接
     * @param string $dbname 数据库名称
     * @param string $user 用户名
     * @param string $psw 密码
     * @param string $charset 可选，默认为utf8
     * @param string $dsn 可选，数据源名称，默认为mysql
     * @param bool $is_auto_commit 可选，默认为TRUE（在OCI，Firebird 以及 MySQL中可用）： 是否自动提交每个单独的语句。
     * @return object 成功返回pdo连接对象，否则返回NULL。
     */
    function pdo_fn_object_get_db_connect($host, $dbname, $user, $psw, $charset = "utf8", $dsn = "mysql", $is_auto_commit = TRUE)
    {
        try {
            $conn = new PDO("$dsn:host={$host};dbname={$dbname};charset={$charset}", $user, $psw);
            if (!$conn) {
                return NULL;
            }
            //（在OCI，Firebird 以及 MySQL中可用）： 是否自动提交每个单独的语句。
            $conn->setAttribute(PDO::ATTR_AUTOCOMMIT, $is_auto_commit);
            //错误报告，抛出 exceptions 异常。
            $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            //启用或禁用预处理语句的模拟。 有些驱动不支持或有限度地支持本地预处理。使用此设置强制PDO总是模拟预处理语句（如果为 TRUE ），
            //或试着使用本地预处理语句（如果为 FALSE）。如果驱动不能成功预处理当前查询，它将总是回到模拟预处理语句上。 需要 bool 类型。
            $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
            return $conn;
        } catch (PDOException $e) {
            return NULL;
        }
    }
}

if (!function_exists('pdo_fn_object_sql_prepare')) {

    /**返回预处理pdo对象
     * @param object $connect 数据库链接
     * @param string $sql sql语句
     * @param array $bind_var [可选] 绑定到sql语句里面的值，sql语里使用":变量名"作为占位符号，"变量名"与$bind_var的键一一对应。<br>
     * 注意:如果传递进来的参数要以数值进行处理的话请一定要*1转换为数值，比如select * from table where id=:key and mid in(:mid)则$bind_var=array("key"=>1, "mid"=>array(1,2,3))
     * @return array 返回预算处理pdo对象
     */
    function pdo_fn_object_sql_prepare($connect, $sql, $bind_var = NULL)
    {
        if ($bind_var) {
            $reg = "/\:([a-z0-9_]+)/i";
            $name_list = array();
            preg_match_all($reg, $sql, $name_list);
            $values = array();
            foreach ($name_list[1] as $name) {
                if (gettype($bind_var[$name]) === gettype(array())) {
                    $tmp = array();
                    for ($i = 0; $i < count($bind_var[$name]); $i++) {
                        $key = $name . $i;
                        $values[$key] = $bind_var[$name][$i];
                        array_push($tmp, ":" . $key);
                    }
                    $mark = implode(",", $tmp);
                    $sql = str_replace(":" . $name, $mark, $sql);
                } else {
                    $values[$name] = $bind_var[$name];
                }
            }
            $st = $connect->prepare($sql);
            //注意foreach的value是使用了引用传递
            //解决方案从这里得知的:http://blog.csdn.net/last_sharly/article/details/47152261
            foreach ($values as $key => &$value) {
                if (gettype($value) == "integer") {
                    $st->bindParam($key, $value, PDO::PARAM_INT);
                } else {
                    $st->bindParam($key, $value);
                }
            }
        } else {
            $st = $connect->prepare($sql);
        }
        return $st;
    }
}

if (!function_exists('pdo_fn_array_select')) {
    /**用来执行sql的select语句
     * @param object $connect 数据库链接
     * @param string $sql sql语句
     * @param array $bind_var [可选] 绑定到sql语句里面的值，sql语里使用":变量名"作为占位符号，"变量名"与$bind_var的键一一对应。<br>
     * 注意:如果传递进来的参数要以数值进行处理的话请一定要*1转换为数值，比如select * from table where id=:key and mid in(:mid)则$bind_var=array("key"=>1, "mid"=>array(1,2,3))
     * @return array 返回一个包含结果集中所有行的键值数组，键为sql语句中的字段名或字段别名(如果有)；如果没有数据则返回空数组。
     */
    function pdo_fn_array_select($connect, $sql, $bind_var = NULL)
    {
        $st = pdo_fn_object_sql_prepare($connect, $sql, $bind_var);
        $st->execute();
        $result = $st->fetchAll(PDO::FETCH_ASSOC);
        return $result;
    }
}

if (!function_exists('pdo_fn_bool_insert')) {

    /** 对指定的表插入记录，支持事务。
     * @param object $connect 数据库链接
     * @param array $table_and_record 键值形式的数据结构，支持多表，多记录插入，array("表名"=>array(array("name"=>"字段名1","address"=>"字段名2")))
     * @param array $id_array 插入记录后记录在数据库的id
     * @param bool $is_transaction 是否启用事务，默认启用。一般不启用的情况是想自行组合增删改查的事务，则需要自行调用$connect的beginTransaction和commit，以及出错时的rollBack
     * @return bool 成功时返回 TRUE， 失败时返回 FALSE。
     */
    function pdo_fn_bool_insert($connect, $table_and_record, &$id_array = array(), $is_transaction = TRUE)
    {
        try {
            //执行批量操作时必须使用事务，即要么全部执行成功，要么失败。
            if ($is_transaction) $connect->beginTransaction();
            foreach ($table_and_record as $table => $record) {
                foreach ($record as $value) {
                    $keys = array_keys($value);
                    $values = array_values($value);
                    $sql = pdo_fn_str_create_string(function () {/*
                  insert into {0} ({1}) values ({2})
                */
                    }, array($table, implode(",", $keys), implode(",", preg_replace("/[\s\S]+/", '?', $keys))));
                    $st = $connect->prepare($sql);
                    $st->execute($values);
                    $id = $connect->lastInsertId("id");
                    $id_array[] = $id;
                }
            }
            if ($is_transaction) $connect->commit();
        } catch (Exception $ex) {
            if ($is_transaction) $connect->rollBack();
            return FALSE;
        }
        return TRUE;
    }
}

if (!function_exists('pdo_fn_bool_execute')) {
    /**执行sql语句
     * @param object $connect 数据库链接
     * @param string $sql sql语句
     * @param array $bind_var [可选] 绑定到sql语句里面的值，sql语里使用":变量名"作为占位符号，"变量名"与$bind_var的键一一对应。<br>
     * @return bool 成功时返回 TRUE， 或者在失败时返回 FALSE。
     */
    function pdo_fn_bool_execute($connect, $sql, $bind_var)
    {
        $st = pdo_fn_object_sql_prepare($connect, $sql, $bind_var);
        return $st->execute();
    }
}

if (!function_exists('pdo_fn_bool_update')) {
    /** 对指定的表更新记录，适用于简单的更新操作，此方法不能在子类重写。
     * <p><b>示例:</b><br/>pdo_fn_bool_update("tableName", array("name"=>"测试名","num1"=>9.1), "where id=:id", array("id"=>10));</p>
     * @param string $connect 数据库链接
     * @param string $table_name 表名
     * @param array $record 键值形式的数据结构，键表示表的字段名
     * @param string $where_sql sql的where条件语句
     * @param array $bind_var 绑定到$where_sql语句里面的值，sql语里使用":变量名"作为占位符号，"变量名"与$bind_var的键一一对应。
     * @return bool 成功时返回 TRUE， 失败时返回 FALSE。
     * @throws ReflectionException
     */
    function pdo_fn_bool_update($connect, $table_name, $record, $where_sql, $bind_var)
    {
        $keys = array();
        foreach ($record as $key => $value) {
            $mkey = preg_replace("/`/", "", $key);
            $bind_var[$mkey] = $value;
            array_push($keys, $key . "=:" . $mkey);
        }
        $sql = pdo_fn_str_create_string(function () {/*
      update {0} set {1} {2}
    */
        }, array($table_name, implode(",", $keys), $where_sql));

        $st = pdo_fn_object_sql_prepare($connect, $sql, $bind_var);
        return $st->execute();
    }
}

if (!function_exists('pdo_fn_bool_delete')) {

    /** 对指定的表删除记录，适用于简单的删除操作，此方法不能在子类重写。
     * @param string $connect 数据库链接
     * @param string $table_name 表名
     * @param string $where_sql sql的where条件语句
     * @param array $bind_var 绑定到$where_sql语句里面的值，sql语里使用":变量名"作为占位符号，"变量名"与$bind_var的键一一对应。
     * @return bool 成功时返回 TRUE， 失败时返回 FALSE。
     * @throws ReflectionException
     */
    function pdo_fn_bool_delete($connect, $table_name, $where_sql, $bind_var)
    {
        $sql = pdo_fn_str_create_string(function () {/*
       delete from {0} {1}
    */
        }, array($table_name, $where_sql));
        $st = pdo_fn_object_sql_prepare($connect, $sql, $bind_var);
        return $st->execute();
    }
}

//示例:
/*
$conn=pdo_fn_object_get_db_connect("localhost", "test", "root", "root");
$sql=pdo_fn_str_create_string(function(){/*
    select * from iwebshop_announcement
* /});
$result=pdo_fn_array_select($conn, $sql);
echo json_encode($result, JSON_UNESCAPED_UNICODE);
$table=array("iwebshop_announcement"=>array(),"iwebshop_article"=>array());
array_push($table["iwebshop_announcement"], array("title"=>"测试标题","content"=>"测试内容","time"=>"2017-03-18"));
array_push($table["iwebshop_article"], array("keywords"=>"测试标题","description"=>"测试内容","color"=>"#000"));
//$result=pdo_fn_bool_insert($conn, $table);
$result=pdo_fn_bool_update($conn, "iwebshop_areas", array("area_name"=>"北京市"),"where area_id=:id", array("id"=>"110000"));
$result=  pdo_fn_bool_delete($conn, "iwebshop_announcement", "where id=:id", array("id"=>"1318"));
echo json_encode($result, JSON_UNESCAPED_UNICODE);
*/